package com.benti.graphic.heatmap;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;

import com.sun.media.jai.codecimpl.PNGImageEncoder;

public class HeatMap {
	protected int baseDotSize = 32;
	protected int step = 5;
	protected int startStep = 0;
	protected int clickLevles = 250;
	protected BufferedImage[] dots;
	private int[] __colors = { 20, 40, 60, 150, 220 };
	private int __low = 0;
	private int __high = 255;
	private int __grey = 220;
	protected int[] rgbArray = createColors();
	protected float alpha = .4f;

	public int getBaseDotSize() {
		return baseDotSize;
	}

	public void setBaseDotSize(int baseDotSize) {
		this.baseDotSize = baseDotSize;
	}

	public int getStep() {
		return step;
	}

	public void setStep(int step) {
		this.step = step;
	}

	public int getStartStep() {
		return startStep;
	}

	public void setStartStep(int startStep) {
		this.startStep = startStep;
	}

	public float getAlpha() {
		return alpha;
	}

	public void setAlpha(float alpha) {
		this.alpha = alpha;
	}

	public HeatMap() {
		super();
		dots = createDots();
	}

	public BufferedImage generate(int width, int height, int[][] points) {
		BufferedImage baseImg = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		BufferedImage workImg = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB);
		int maxClicks = drawPixels(baseImg, points,width,height);
		composite(workImg, baseImg, width, height, maxClicks);
		drawRainbow(workImg, maxClicks);
		colorize(workImg, width, height);
		baseImg = null;
		return workImg;
	}
 
	protected BufferedImage[] createDots() {
		BufferedImage[] sdots = new BufferedImage[clickLevles];
		for (int i = 0; i < clickLevles; i++) {
			int dotSize = getDotSize(i);
			sdots[i] = new BufferedImage(dotSize, dotSize,
					BufferedImage.TYPE_INT_RGB);
			for (int x = 0; x < dotSize; x++) {
				for (int y = 0; y < dotSize; y++) {
					double sinX = Math.sin(x * Math.PI / dotSize);
					double sinY = Math.sin(y * Math.PI / dotSize);

					double alpha = 255 - (i * sinX * sinX * sinY * sinY)
							/ clickLevles * 255;
					int a = (int) alpha;
					int c = new Color(a, a, a).getRGB();
					imagesetpixel(sdots[i], x, y, c);
					// if (i == 60 || i == 127) {
					// if ((x == 10 && y == 10) || (x == 0 && y == 0)) {
					// int r = getRedFromRGB(c);
					// // System.out.println(i + ": " + x + "," + y + " ->
					// // "
					// // + (int) alpha + " r= " + r);
					// }
					// }
				}
			}
		}
		return sdots;
	}

	protected void imagesetpixel(BufferedImage img, int x, int y, int c) {
		img.setRGB(x, y, c);
	}

	protected int imagecolorat(BufferedImage img, int x, int y) {
		return img.getRGB(x, y);
	}

	protected int drawPixels(BufferedImage baseImg, int[][] points,int width, int height) {
		int maxClicks = 0;
		synchronized (points) {
			for (int i = 0; i < points.length; i++) {
				int x = points[i][0];
				int y = points[i][1];
				x -= x % this.step - this.startStep;
				y -= y % this.step - this.startStep;
				if(x>width||y>height)
					continue;
				int c = imagecolorat(baseImg, x, y);
				c++;  //deepening the color (color value added)
				imagesetpixel(baseImg, x, y, c);
				maxClicks = Math.max(maxClicks, c + 16777216);////record the deepest color point  of  points
			}
		}
		return maxClicks;

	}

	public void imagecopy(BufferedImage toImg, BufferedImage srcImg, int ox,
			int oy, int width, int height) {
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				if (ox + x > 0 && oy + y > 0) {
					int ta = getRedFromRGB(toImg.getRGB(ox + x, oy + y));
					int sa = getRedFromRGB(srcImg.getRGB(x, y));
					int ra = (int) ((float) ta / 255 * sa);
					toImg.setRGB(ox + x, oy + y, getRGB(ra, ra, ra, 255));
				}
			}
		}
	}

	protected static void imagefilledrectangle(BufferedImage img, int x, int y,
			int width, int height, Color c) {
		img.getGraphics().setColor(c);
		img.getGraphics().fillRect(x, y, width, height);
	}

	public int[] createColors() {
		int[] rgbs = new int[clickLevles];
		int[][] colors = new int[clickLevles][3];
		for (int i = 0; i < clickLevles; i++) {
			int high = this.__high;
			int low = this.__low + 160;
			/** Red */
			if (i < __colors[0]) {
				colors[i][0] = this.__grey + (low - this.__grey) * i
						/ this.__colors[0];
			} else if (i < this.__colors[2]) {
				colors[i][0] = low;
			} else if (i < this.__colors[3]) {
				colors[i][0] = low + (high - low) * (i - this.__colors[2])
						/ (this.__colors[3] - this.__colors[2]);
			} else {
				colors[i][0] = high;
			}
			high = this.__high - 80;
			low = this.__low;
			/** Green */
			if (i < this.__colors[3]) {
				colors[i][1] = this.__grey;
			} else {
				colors[i][1] = this.__grey - (this.__grey - low)
						* (i - this.__colors[3])
						/ (clickLevles - 1 - this.__colors[3]);
			}
			high = this.__high - 160;
			low = this.__low;
			/** Blue */
			if (i < this.__colors[0]) {
				colors[i][2] = this.__grey + (high - this.__grey) * i
						/ this.__colors[0];
			} else if (i < this.__colors[1]) {
				colors[i][2] = high;
			} else if (i < this.__colors[2]) {
				colors[i][2] = high - (high - low) * (i - this.__colors[1])
						/ (this.__colors[2] - this.__colors[1]);
			} else {
				colors[i][2] = low;
			}
			/** a */
			int a;
			if (i < __colors[0]) {
				a = this.__low + (this.__high - this.__low) * i
						/ this.__colors[0];
			} else {
				a = this.__high;
			}
			/** RGB* */
			rgbs[i] = getRGB(colors[i][0], colors[i][1], colors[i][2], 255);
			// int a=(rgbs[i] >> 16) & 0xFF;
			// System.out.println(i+": -> "+rgbs[i]+" r= "+a);
		}
		return rgbs;
	}

	public void composite(BufferedImage workImg, BufferedImage baseImg,
			int width, int height, int maxClicks) {
		imagefilledrectangle(workImg, 0, 0, width, height, Color.WHITE);
		// System.out.println(maxClicks);
		for (int x = this.startStep; x < width; x += this.step) {
			for (int y = this.startStep; y < height; y += this.step) {
				float c = imagecolorat(baseImg, x, y) + 16777216;
				float dot = (c / maxClicks) * clickLevles;
				if (dot != 0) {
					dot--;
					int dotSize = getDotSize((int) dot);
					int w = dotSize;
					if (x + dotSize / 2 > width)
						w = width - x + dotSize / 2;
					int h = dotSize;
					if (y + dotSize / 2 > height)
						h = height - y + dotSize / 2;
					imagecopy(workImg, dots[(int) dot],
							(int) (x - dotSize / 2), (int) (y - dotSize / 2),
							w, h);
					/*
					 * g2.drawImage(dots[(int) dot], (int) (x - dotSize / 2),
					 * (int) (y - dotSize / 2), null);
					 */
				}
			}
		}

	}

	public void colorize(BufferedImage workImg, int width, int height) {
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				int c = workImg.getRGB(x, y);
				float red = (c >> 16) & 0xFF;
				float f = clickLevles - 1 - red / 256 * clickLevles;
				if (f >= clickLevles) {
					f = clickLevles - 1;
				} else if (f < 0) {
					f = 0;
				}
				workImg.setRGB(x, y, rgbArray[(int) f]);
			}
		}
	}

	private void drawRainbow(BufferedImage workImg, int maxClicks) {
		Graphics2D g2 = (Graphics2D) workImg.getGraphics();
		for (int i = 0; i < clickLevles; i++) {
			float m = 255 - ((float) i) / clickLevles * 255;
			Color c = new Color((int) m, (int) m, (int) m);
			g2.setColor(c);
			g2.drawLine(i, 0, i, 10);
		}
		if (maxClicks > 15) {
			g2.drawString(String.valueOf(1), 1, 22);
			g2.drawString(String.valueOf(maxClicks / 5), clickLevles / 5, 22);
			g2.drawLine(clickLevles / 5, 6, clickLevles / 5, 10);
			g2.drawString(String.valueOf(maxClicks / 5 * 2),
					clickLevles / 5 * 2, 22);
			g2.drawLine(clickLevles / 5 * 2, 6, clickLevles / 5 * 2, 10);
			g2.drawString(String.valueOf(maxClicks / 5 * 3),
					clickLevles / 5 * 3, 22);
			g2.drawLine(clickLevles / 5 * 3, 6, clickLevles / 5 * 3, 10);
			g2.drawString(String.valueOf(maxClicks / 5 * 4),
					clickLevles / 5 * 4, 22);
			g2.drawLine(clickLevles / 5 * 4, 6, clickLevles / 5 * 4, 10);
			g2.drawString(String.valueOf(maxClicks), clickLevles, 22);

		} else {
			for (int i = 0; i < maxClicks; i++) {
				int x = (int) ((float) i / maxClicks * clickLevles);
				g2.drawString(String.valueOf(i + 1), x, 22);
				g2.drawLine(x, 6, x, 10);
			}
		}

	}

	private int getDotSize(int i) {
		return (int) ((1 + (float) i / clickLevles) * baseDotSize);
	}

	private static int getRGB(int r, int g, int b, int a) {
		return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8)
				| ((b & 0xFF) << 0);
	}

	private static int getRedFromRGB(int c) {
		return (c >> 16) & 0xFF;
	}

	public static void OverComposite(BufferedImage srcImg,
			BufferedImage workImg, float alpha) {
		Graphics2D g2 = (Graphics2D) srcImg.getGraphics();
		Composite composite = AlphaComposite.getInstance(
				AlphaComposite.SRC_OVER, alpha);
		g2.setComposite(composite);
		g2.drawImage(workImg, 0, 0, null);
	}

	public static void writePNG(OutputStream out, BufferedImage workImg)
			throws IOException {
		if (null != workImg) {
			PNGImageEncoder encoder = new PNGImageEncoder(out, null);
			encoder.encode(workImg);
		}
	}

	public static BufferedImage createAlphaImg(int width, int height) {
		try {
			BufferedImage baseImg = new BufferedImage(width, height,
					BufferedImage.TYPE_INT_ARGB);
			// BufferedImage baseImg = ImageIO.read(new File("/tmp/a.png"));
			System.out.println(baseImg.getType());
			// imagefilledrectangle(baseImg, 0, 0, width, height, Color.WHITE);
			Graphics2D g2D = baseImg.createGraphics();
			// Set best alpha interpolation quality
			g2D.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
					RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);

			// Clear image with transparent alpha by drawing a rectangle
			g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR,
					0.0f));
			Rectangle2D.Double rect = new Rectangle2D.Double(0, 0, width,
					height);
			g2D.fill(rect);
			return baseImg;
		} catch (Exception e) {

			e.printStackTrace();
			return null;
		}
	}

}
